package brickdestroyer.simulation;

import brickdestroyer.math.Circle2D;
import brickdestroyer.math.DynamicShape2D;
import brickdestroyer.math.Rectangle2D;
import brickdestroyer.math.Shape2D;
import brickdestroyer.math.Vector2D;

/**
 * This class is responsible for managing all collisions
 * in the game.
 * <p>It is required to determine if there is a collision
 * between two given objects and do the necessary displacement
 * and speed updates.
 * <p>The current implemenetation uses the axis theorem. It roughly states
 * that if there is any axis by which the projection of two convex
 * shapes do not intersect then those two shapes do not intersect.
 * <br>That way one should find all necessary (pissible and necessary distinct)
 * axes by which there could be an intersection in order to come to a
 * result.
 * 
 * @author Momchil Atanasov
 */
public final class CollisionManager {

    private final Simulation simulation;

    public CollisionManager(Simulation simulation) {
        this.simulation = simulation;
    }

    /**
     * Checks if there is a collision between two shapes.
     * <p>It will separate them and perform the necessary speed
     * changes if atleast one of the two shapes implements the
     * DynamicShape2D interface.
     * @see DynamicShape2D
     * @return <code>true</code> if there is a collision,
     * <code>false</code> otherwise.
     */
    public boolean checkCollision(Shape2D shapeA, Shape2D shapeB) {
        if (shapeA == null || shapeB == null) {
            return false;
        }

        if ((shapeA instanceof Circle2D) && (shapeB instanceof Rectangle2D)) {
            return checkCollision((Circle2D)shapeA, (Rectangle2D)shapeB);
        }
        
        return false;
    }

    /**
     * Checks if there is a collision between a circle and a rectangle
     * shape.
     * <p>It will separate them and perform the necessary speed
     * changes if atleast one of the two shapes implements the
     * DynamicShape2D interface.
     * @see DynamicShape2D
     * @return <code>true</code> if there is a collision,
     * <code>false</code> otherwise.
     */
    public boolean checkCollision(Circle2D circle, Rectangle2D rectangle) {
        // We check if there is a horizontal overlap and
        // calculate the necessary object move if needed
        Vector2D horizontalMove;
        boolean horizontalOverlap = (circle.getMaxX() >= rectangle.getMinX()) &&
                                    (circle.getMinX() <= rectangle.getMaxX());
        if (horizontalOverlap) {
            if (circle.x < rectangle.x) {
                horizontalMove = new Vector2D(rectangle.getMinX() - circle.getMaxX(), 0.0f);
            } else {
                horizontalMove = new Vector2D(rectangle.getMaxX() - circle.getMinX(), 0.0f);
            }
        } else {
            return false;
        }

        // We check if there is a vertical overlap and
        // calculate the necessary object move if needed
        Vector2D verticalMove;
        boolean verticalOverlap = (circle.getMaxY() >= rectangle.getMinY()) &&
                                  (circle.getMinY() <= rectangle.getMaxY());
        if (verticalOverlap) {
            if (circle.y < rectangle.y) {
                verticalMove = new Vector2D(0.0f, rectangle.getMinY() - circle.getMaxY());
            } else {
                verticalMove = new Vector2D(0.0f, rectangle.getMaxY() - circle.getMinY());
            }
        } else {
            return false;
        }

        // We check if the circle overlaps a corner of the rectangle. If
        // it does then this is the shortest object movement and we do it
        // otherwise we use the smallest of the horizontal or vertical movements.
        Vector2D objectMove;
        boolean cornerOverlapPossible =
                ((circle.x < rectangle.getMinX()) || (circle.x > rectangle.getMaxX())) &&
                ((circle.y < rectangle.getMinY()) || (circle.y > rectangle.getMaxY()));
        if (cornerOverlapPossible) {
            Vector2D closestCorner = getClosestRectangleCorner(circle, rectangle);
            objectMove = new Vector2D(circle.x, circle.y);
            objectMove.dec(closestCorner);
            // We check if there is at all an overlap with the corner
            float objectMoveDistance = objectMove.getLength();
            if (objectMoveDistance > circle.getRadius()) {
                return false;
            }
            objectMove.setLength(circle.getRadius() - objectMoveDistance);
        } else {
            objectMove = (horizontalMove.getLengthSqr() < verticalMove.getLengthSqr())
                         ? horizontalMove : verticalMove;
        }

        // So far we know that both objects overlap and we have a vector for
        // their detachment. Now we need to do the detachment and evaluate new
        // speeds for them.
        separateShapes(circle, rectangle, objectMove);
        return true;
    }

    /*
     * This method determines which of the two (if not both) shapes
     * could be moved and then performs the move.
     */
    private void separateShapes(Shape2D shapeA, Shape2D shapeB, Vector2D objectMove) {
        boolean shapeAMovable = shapeA instanceof DynamicShape2D;
        boolean shapeBMovable = shapeB instanceof DynamicShape2D;

        if (!shapeAMovable && !shapeBMovable) {
            return;
        }

        if (shapeAMovable && !shapeBMovable) {
            displaceShape(shapeA, objectMove);
            return;
        }

        if (!shapeAMovable && shapeBMovable) {
            objectMove.mul(-1.0f);
            displaceShape(shapeB, objectMove);
            return;
        }

        if (shapeAMovable && shapeBMovable) {
            return;
        }

    }

    /**
     * This method displaces the given shape by the displacement
     * vector passed as parameter. It also does the necessary
     * speed recalculations.
     * @param shape shape to be displaceed
     * @param displacement displacement vector
     */
    private void displaceShape(Shape2D shape, Vector2D displacement) {
        // Displace shape out of collision
        shape.inc(displacement);
        DynamicShape2D shapeDynamics = (DynamicShape2D)shape;

        // Calculate new speed and direction
        displacement.setLength(1.0f);
        float hitSpeed = displacement.dotProduct(shapeDynamics.getSpeed());
        Vector2D bounceVector = displacement.getMul(hitSpeed);
        Vector2D slideVector = shapeDynamics.getSpeed().getDec(bounceVector);

        PhysicsManager physicsManager = simulation.getPhysicsManager();
        bounceVector.mul(-physicsManager.getBounce());
        slideVector.mul(physicsManager.getSlide());

        shapeDynamics.getSpeed().setTo(bounceVector);
        shapeDynamics.getSpeed().inc(slideVector);
    }

    /*
     * Returns the position of the closes rectangle corner
     * to a given position.
     */
    private static Vector2D getClosestRectangleCorner(Vector2D point, Rectangle2D rectangle) {
        Vector2D result = new Vector2D();
        result.x = (point.x < rectangle.x) ? rectangle.getMinX() : rectangle.getMaxX();
        result.y = (point.y < rectangle.y) ? rectangle.getMinY() : rectangle.getMaxY();
        return result;
    }
        
}
